
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>4 类 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch05.html" />
    
    
    <link rel="prev" href="ch03.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >4 类</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="47"></a></p>
<div class="chapter-number"><p class="chapter-number">4</p></div>

<h1 id="class">类 </h1>
<blockquote>
<p>那些类型并不“抽象”，</p>
<p>它们跟<code>int</code>和<code>float</code>一样真实。</p>
<p>—— 道格·麦克罗伊</p>
</blockquote>
<h2 id="4.1">4.1 导言 </h2>
<p>本章及后续三章在避免牵涉过多细节的情况下，阐述了C++对抽象和资源管理的支持：</p>
<ul>
<li>本章简略地介绍定义及使用新类型（用户定义类型）的方式。
  具体而言，介绍了定义和使用<em>实体类（concrete class）</em>、
  <em>抽象类（abstract class）</em>和<em>类层次（class hierarchy）</em>
  要用到的基本属性、实现技术及语言构造。</li>
<li>第5章介绍C++明确定义了意义的操作，比如构造函数、析构函数和赋值。
  勾勒出一个规则——把这些规则综合运用以控制对象生命周期，
  并且支持简洁、高效且完善的资源管理。</li>
<li>第6章介绍模板，一种利用（其它）类型和算法把类型和算法参数化的机制。
  以函数表示的施加在用户定义类型和内置类型之上的运算，有时会泛化为
  <em>模板函数（template function）</em>和<em>函数对象（function object）</em>。</li>
<li>第7章是泛型编程底层的概念、技术及语言特性的概览。
  着重于<em>概束（concept）</em>的定义和使用，从而精确地给出模板的接口并指导设计。
  <em>可变参数模板（variadic template）</em>
  的问世是为了阐释最通用同时也最灵活的接口。</li>
</ul>
<p><a class="en-page-number" id="48"></a></p>
<p>这语言构造被用来支持名为
<em>面向对象编程（object-oriented programming）</em>和
<em>泛型编程（generic programming）</em>的编程风格。
随后的第8-15章给出标准库的示例及应用。</p>
<p>C++的主要语言构造是<em>类（class）</em>。类是用户定义类型，在程序代码中代表某个概念。
每当程序的设计中出现一个概念、构想或实体等，我们就试图用一个类去代表它，
使其在代码里具象化，而不仅仅存在于脑子、设计文档或注释里。
借助一套精选的类构建的应用程序，远比直接用内置类型来得容易理解和靠谱。
特别是，程序库通常以类的形式呈现。</p>
<p>除类基本类型、运算符和语句之外的所有语言构造，
从本质上讲都是用来辅助定义更好的类以及更便捷地使用它们。
“更好”的意思是更准确、更易于维护、更高效、更优雅、更顺手、更具可读性、更符合直觉。
多数编程技术有赖于特定种类的类的设计和实现。程序员的需求和品味天差地别。
因此对类的支援近乎广阔无边。此处仅介绍对三种重要的类提供的支持：</p>
<ul>
<li>实体类(§4.2)</li>
<li>抽象类(§4.3)</li>
<li>类层次中的类(§4.2)</li>
</ul>
<p>浩如烟海的实用的类都可以被归为其中某一种。
还有更多的类是它们的细微变种或者用它们组合出来的。</p>
<h2 id="4.2">4.2 实体类型 </h2>
<p><em>实体类（concrete class）</em>的基本思想是：它们的表现“恰如内置类型一样”。
例如：复数类和无限精度的整数类都非常类似于内置的<code>int</code>，
当然，它们各自特色的语意和运算除外。
与之类似，<code>vector</code>和<code>string</code>都类似于内置的数组，
但却能青出于蓝(§9.2, §10.3, §11.2)。</p>
<p>实体类型在定义方面的特征是：它的<span title="请原谅我硬搞出来一个词，因为它在本章出现了太多次。其实按我的理解，更合理的说法是“用到的所有数据”，本质上是在说“数据”，可是仅译成“数据”容易跟“data”混淆；而“representation”在这是名词“表示”的意思，说成“用到的所有数据的表示”又太拗口。如果改成“数据表示”，容易被误会成主谓短语，用在句子的语境中，容易引起误解；改成“表示数据”又容易被误会成动宾短语，引发类似的误解。所以我把“表示”改成了“表征”，这样无论理解成“名词+名词”还是“形容词+名词”的偏正短语，意思都是“什么什么样的数据”，同时还能跟单纯的“数据（data）”区分开来。">表征数据（representation）</span><sup><a href="#fn_1" id="reffn_1">1</a></sup>位于定义之中。
在许多重要的案例中，比如<code>vector</code>，
其表征数据是一个或多个指针——指向存储在别处的数据，
但其表征数据却出现在实体类的每个对象之中。
这使得具体的对象在运行时间和存储空间方面都效率上佳。
具体来说，它允许我们：</p>
<ul>
<li>把实体类型的对象放在栈上、静态分配的内存里以及其它对象中(§1.5)；</li>
<li>直接使用该对象（而不必借助指针或引用）；</li>
<li>（定义后）立即并且完整地初始化对象（即利用构造函数；§2.3），以及</li>
<li>复制或转移对象(§5.2)。</li>
</ul>
<p>表征数据可以是私有的（像<code>Vector</code>的情形；§2.3）
并且仅允许通过成员函数访问，但它的存在是毫无疑问的。
因此，一旦表征数据有任何实质性的变动，用户就必须重新编译。
这是让实体类型跟内置类型行为一致的必要代价。</p>
<p><a class="en-page-number" id="49"></a></p>
<p>对于变动不频繁的类型，并且局部变量给出了关键的明确性和高效性的情况下，
这种重新编译是可接受的，而且通常都比较理想。
为增强灵活性，实体类型可以把其表征数据的绝大部分放在自由存储区（动态内存，堆）里，
并通过放置在类对象内的部分进行存取。
<code>vector</code>和<code>string</code>就是这样实现的；
它们可以被视为具有精心打造接口的资源操控器（resource handle）。</p>
<h3 id="4.2.1">4.2.1 算术类型 </h3>
<p>“经典的用户定义算术类型”是<code>complex</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">complex</span> {
    <span class="hljs-type">double</span> re, im;      <span class="hljs-comment">// 表征数据：两个double</span>
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">complex</span>(<span class="hljs-type">double</span> r, <span class="hljs-type">double</span> i) :re{r}, im{i} {}    <span class="hljs-comment">// 用两个标量构造complex</span>
    <span class="hljs-built_in">complex</span>(<span class="hljs-type">double</span> r) :re{r}, im{<span class="hljs-number">0</span>} {}              <span class="hljs-comment">// 用一个标量构造complex</span>
    <span class="hljs-built_in">complex</span>() :re{<span class="hljs-number">0</span>}, im{<span class="hljs-number">0</span>} {}                      <span class="hljs-comment">// complex的默认值：{0,0}</span>

    <span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">real</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> re; }
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">real</span><span class="hljs-params">(<span class="hljs-type">double</span> d)</span> </span>{ re=d; }
    <span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">imag</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> im; }
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">imag</span><span class="hljs-params">(<span class="hljs-type">double</span> d)</span> </span>{ im=d; }

    complex&amp; <span class="hljs-keyword">operator</span>+=(complex z)
    {
        re+=z.re;       <span class="hljs-comment">// 加至re和im</span>
        im+=z.im;
        <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;   <span class="hljs-comment">// 返回结果</span>
    }

    complex&amp; <span class="hljs-keyword">operator</span>-=(complex z)
    {
        re-=z.re;
        im-=z.im;
        <span class="hljs-keyword">return</span> *<span class="hljs-keyword">this</span>;
    }

    complex&amp; <span class="hljs-keyword">operator</span>*=(complex);   <span class="hljs-comment">// 定义在类外某处</span>
    complex&amp; <span class="hljs-keyword">operator</span>/=(complex);   <span class="hljs-comment">// 定义在类外某处</span>
};
</code></pre>
<p>这是个略微简化版本的标准库<code>complex</code>(§14.4)。
类定义本身仅包含特定运算——那些需要访问表征数据的。
表征数据是简单且常规的。
由于实用性原因，它必须兼容 Fortran 60年前定义的版本，所以需要一套常规运算。
除逻辑方面的要求之外，<code>complex</code>还必须高效，否则就没什么用了。
这意味着简单的运算必须是内联的。
就是说简单运算（如构造函数、<code>+=</code>和<code>imag()</code>）的实现，在机器代码里不能有函数调用。
在类里定义的函数，默认就是内联的。
也可以显式要求内联——在函数声明前使用关键字<code>inline</code>。</p>
<p><a class="en-page-number" id="50"></a></p>
<p>具备工业强度的<code>complex</code>（比如标准库里那个）在是否内联方面的权衡很谨慎。</p>
<p>能够不带参数调用的构造函数叫<em>默认构造函数（default constructor）</em>。
所以<code>complex()</code>就是<code>complex</code>的默认构造函数。
类型定义过默认构造函数后，就为这种类型消除了未初始化变量的隐患。</p>
<p>返回实部和虚部的函数用了说明符<code>const</code>，
意思是此函数通过某个对象调被用时，不会修改该对象。
<code>const</code>成员函数既可以通过<code>const</code>对象也可以通过非<code>const</code>对象调用，
但是非<code>const</code>成员函数只能通过非<code>const</code>对象调用。例如：</p>
<pre><code class="lang-cpp">complex z = {<span class="hljs-number">1</span>,<span class="hljs-number">0</span>};
<span class="hljs-type">const</span> complex cz {<span class="hljs-number">1</span>,<span class="hljs-number">3</span>};
z = cz;                 <span class="hljs-comment">// OK：赋值给非const变量</span>
cz = z;                 <span class="hljs-comment">// 错误：complex::operator=()是个非const成员函数</span>
<span class="hljs-type">double</span> x = z.<span class="hljs-built_in">real</span>();    <span class="hljs-comment">// OK: complex::real() 是个const成员函数</span>
</code></pre>
<p>很多有用的运算无需直接访问<code>complex</code>的表征数据，因此可以与类定义分开：</p>
<pre><code class="lang-cpp">complex <span class="hljs-keyword">operator</span>+(complex a, complex b) { <span class="hljs-keyword">return</span> a+=b; }
complex <span class="hljs-keyword">operator</span>-(complex a, complex b) { <span class="hljs-keyword">return</span> a-=b; }
complex <span class="hljs-keyword">operator</span>-(complex a) { <span class="hljs-keyword">return</span> {-a.<span class="hljs-built_in">real</span>(), -a.<span class="hljs-built_in">imag</span>()}; } <span class="hljs-comment">// 一元负号</span>
complex <span class="hljs-keyword">operator</span>*(complex a, complex b) { <span class="hljs-keyword">return</span> a*=b; }
complex <span class="hljs-keyword">operator</span>/(complex a, complex b) { <span class="hljs-keyword">return</span> a/=b; }
</code></pre>
<p>这个例子利用了参数传值的特性，因此修改参数不会影响调用者（作为参数）使用的变量，
还可以把结果用作返回值。</p>
<p><code>==</code>和<code>!=</code>就很直白了：</p>
<pre><code class="lang-cpp"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>==(complex a, complex b)   <span class="hljs-comment">// 相等</span>
{
    <span class="hljs-keyword">return</span> a.<span class="hljs-built_in">real</span>()==b.<span class="hljs-built_in">real</span>() &amp;&amp; a.<span class="hljs-built_in">imag</span>()==b.<span class="hljs-built_in">imag</span>();
}
<span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>!=(complex a, complex b)   <span class="hljs-comment">// 不等</span>
{
    <span class="hljs-keyword">return</span> !(a==b);
}
<span class="hljs-function">complex <span class="hljs-title">sqrt</span><span class="hljs-params">(complex)</span></span>;      <span class="hljs-comment">// 定义在别处</span>
<span class="hljs-comment">// ...</span>
</code></pre>
<p><code>complex</code>类可以这样用：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(complex z)</span>
</span>{
    complex a {<span class="hljs-number">2.3</span>};    <span class="hljs-comment">// 从 2.3 构造出 {2.3,0.0}</span>
    complex b {<span class="hljs-number">1</span>/a};
    complex c {a+z*complex{<span class="hljs-number">1</span>,<span class="hljs-number">2.3</span>}};
</code></pre>
<p><a class="en-page-number" id="51"></a></p>
<pre><code class="lang-cpp">    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">if</span> (c != b)
        c = -(b/a)+<span class="hljs-number">2</span>*b;
}
</code></pre>
<p>编译器会把涉及<code>complex</code>数值的运算符转换成相应的函数调用。
例如：<code>c!=b</code>对应<code>operator!=(c,b)</code>，<code>1/a</code>对应<code>operator/(complex{1},a)</code>。</p>
<p>用户定义的运算符（“重载运算符（overloaded operator）”）
应该谨慎并且遵循约定俗成的规则使用。
重载运算符的语法是语言规定好的，所以你无法定义出一元的<code>/</code>。
另外，不可以修改内置类型运算符的含义，所以你不能把<code>+</code>重定义成<code>int</code>的减法。</p>
<h3 id="4.2.2">4.2.2 容器 </h3>
<p><em>容器（container）</em>是承载元素集合的对象。
我们把<code>Vector</code>称为容器，因为<code>Vector</code>类型的对象是容器。
如 §2.3 所定义，<code>Vector</code>作为<code>double</code>的容器是顺理成章的：
它易于理解，建立了有用的不变式(§3.5.2)，对存取操作提供了区间有效性检查，
提供了<code>size()</code>以便使用它对元素进行循环访问。
但是它有个致命的缺陷：它用<code>new</code>给元素分配空间，却从未释放它们。
这就不太妙了，因为尽管C++定义了垃圾回收(§5.3)接口，
但却不能确保有个垃圾回收器把未使用的内存供新对象使用。
某些情况下你无法使用垃圾回收器，更常见的情形是：
出于逻辑和性能原因，你倾向于更精细地控制销毁行为。
我们需要一个机制确保把构造函数分配的内存释放掉；
这个机制就是<em>析构函数（destructor）</em>：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s) :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[s]}, sz{s}   <span class="hljs-comment">// 构造函数：申请资源</span>
    {
        <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=s; ++i)    <span class="hljs-comment">// 初始化元素</span>
            elem[i]=<span class="hljs-number">0</span>;
    }

    ~<span class="hljs-built_in">Vector</span>() { <span class="hljs-keyword">delete</span>[] elem; }    <span class="hljs-comment">// 析构函数：释放资源</span>

    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span></span>;
<span class="hljs-keyword">private</span>:
    <span class="hljs-type">double</span>* elem;   <span class="hljs-comment">// elem指向一个数组，该数组承载sz个double</span>
    <span class="hljs-type">int</span> sz;
};
</code></pre>
<p>析构函数的名称是取补运算符<code>~</code>后跟类名；它跟构造函数互补。
<code>Vector</code>的构造函数用<code>new</code>运算符在自由存储区
（也叫<em>堆（heap）</em>或<em>动态存储区（dynamic store）</em>）里分配了一些内存。
析构函数去清理——使用<code>delete[]</code>运算符释放那块内存。
普通<code>delete</code>删除单个对象，<code>delete[]</code>删除数组。</p>
<p>这些操作都不会干涉到<code>Vector</code>用户。
用户仅仅创建并使用<code>Vector</code>，就像对内置类型一样。例如：</p>
<p><a class="en-page-number" id="52"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fct</span><span class="hljs-params">(<span class="hljs-type">int</span> n)</span>
</span>{
    <span class="hljs-function">Vector <span class="hljs-title">v</span><span class="hljs-params">(n)</span></span>;
    <span class="hljs-comment">// ... 使用 v ...</span>
    {
        <span class="hljs-function">Vector <span class="hljs-title">v2</span><span class="hljs-params">(<span class="hljs-number">2</span>*n)</span></span>;
        <span class="hljs-comment">// ... 使用 v 和 v2 ...</span>
    } <span class="hljs-comment">// v2 在此被销毁</span>
    <span class="hljs-comment">// ... 使用 v ..</span>
} <span class="hljs-comment">// v 在此被销毁</span>
</code></pre>
<p>像<code>int</code>和<code>char</code>这些内置类型一样，<code>Vector</code>遵循相同的命名、作用域、
内存分配、生命期等一系列规则(§1.5)。
此处的<code>Vector</code>版本为简化而略掉了错误处理；参见 §3.5。</p>
<p>构造函数/析构函数 这对组合是很多优雅技术的根基。
确切的说，它是C++大多数资源管理技术(§5.3, §13.2)的根基。
考虑如下的<code>Vector</code>图示：</p>
<p><img src="img/ch04_01.png" alt="Vector-initialization"></p>
<p>构造函数分配这些元素并初始化<code>Vector</code>响应的成员变量。析构函数释放这些元素。
这个<em>数据操控器模型（handle-to-data model）</em>常见于数据管理，
管理那些容量在对象生命期内可能变化的数据。
这个构造函数申请资源、析构函数释放资源的技术叫做
<em>资源请求即初始化（Resource Acquisition Is Initialization）</em>
或者RAII，为我们消灭“裸的<code>new</code>操作”，
就是说，避免在常规代码中进行内存分配，将其隐匿于抽象良好的实现中。
与之类似，“裸的<code>delete</code>操作”也该竭力避免。
避免裸<code>new</code>和裸<code>delete</code>，能大大降低代码出错的几率，
也更容易避免资源泄漏(§13.2)。</p>
<h3 id="4.2.3">4.2.3 初始化容器 </h3>
<p>容器的作用是承载元素，因此很明显需要便利的方法把元素放入容器。
可以创建元素数量适宜的<code>Vector</code>，然后给这些元素赋值，但还有些更优雅的方式。
此处介绍其中颇受青睐的两种：</p>
<ul>
<li><em>初始化列表构造函数（initializer-list constructor）</em>：
  用一个元素列表进行初始化。</li>
<li><code>push_back()</code>：在序列的末尾（之后）添加一个新元素。</li>
</ul>
<p>它们可以这样声明：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(std::initializer_list&lt;<span class="hljs-type">double</span>&gt;);  <span class="hljs-comment">// 用一个double列表初始化</span>
    <span class="hljs-comment">// ...</span>
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">push_back</span><span class="hljs-params">(<span class="hljs-type">double</span>)</span></span>;                 <span class="hljs-comment">// 在末尾新增元素，把容量加一</span>
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p><a class="en-page-number" id="53"></a></p>
<p>在输入任意数量元素的时候，<code>push_back()</code>很有用，例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Vector <span class="hljs-title">read</span><span class="hljs-params">(istream&amp; is)</span>
</span>{
    Vector v;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">double</span> d; is&gt;&gt;d; )     <span class="hljs-comment">// read floating-point values into d</span>
        v.<span class="hljs-built_in">push_back</span>(d);         <span class="hljs-comment">// add d to v return v;</span>
}
</code></pre>
<p>循环的终止条件是文件终止或格式错误。
在终止之前，每个读入的数字都被添加到<code>Vector</code>，
因此在函数结束的时候，<code>v</code>的容量是读入的元素数量。
我用了<code>for</code>循环，而非按惯例的<code>while</code>循环，目的是把<code>d</code>的作用域限制在循环内。
可以为<code>Vector</code>定义一个转移构造函数，以便在<code>read()</code>返回大量数据时的运行成本低廉，
§5.2.2 阐释此内容：</p>
<pre><code class="lang-cpp">Vector v = <span class="hljs-built_in">read</span>(cin);   <span class="hljs-comment">// 此处未对Vector的元素进行复制</span>
</code></pre>
<p>有关<code>std::vector</code>提供的<code>push_back()</code>以及其它高效地修改<code>vector</code>容量的方式，
详见§11.2。</p>
<p>用于定义初始化列表构造函数的<code>std::initializer_list</code>是个标准库中的类型，
编译器对它有所了解：当我们用<code>{}</code>列表，比如<code>{1,2,3,4}</code>的时候，
编译器会为程序创建一个<code>initializer_list</code>对象。
因此，可以这样写：</p>
<pre><code class="lang-cpp">Vector v1 = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>};            <span class="hljs-comment">// v1有5个元素</span>
Vector v2 = {<span class="hljs-number">1.23</span>, <span class="hljs-number">3.45</span>, <span class="hljs-number">6.7</span>, <span class="hljs-number">8</span>};   <span class="hljs-comment">// v2有4个元素</span>
</code></pre>
<p><code>Vector</code>的初始化列表构造函数可能长这样：</p>
<pre><code class="lang-cpp">Vector::<span class="hljs-built_in">Vector</span>(std::initializer_list&lt;<span class="hljs-type">double</span>&gt; lst)   <span class="hljs-comment">// 用列表初始化</span>
    :elem{<span class="hljs-keyword">new</span> <span class="hljs-type">double</span>[lst.<span class="hljs-built_in">size</span>()]}, sz{<span class="hljs-built_in">static_cast</span>&lt;<span class="hljs-type">int</span>&gt;(lst.<span class="hljs-built_in">size</span>())}
{
    <span class="hljs-built_in">copy</span>(lst.<span class="hljs-built_in">begin</span>(),lst.<span class="hljs-built_in">end</span>(),elem);       <span class="hljs-comment">// 从lst复制到elem(§12.6)</span>
}
</code></pre>
<p>很遗憾，标准库为容量和下标选择了<code>unsigned</code>整数，
所以我需要用丑陋的<code>static_cast</code>把初始化列表的容量显式转换成<code>int</code>。
这个选择有点书呆子气了，因为手写列表的元素数量基本不会超出有符号整数的上限
（16位整数是32,767，32位整数是2,147,483,647）。
但是类型系统没有常识。
它仅仅知道变量的取值值范围，而非具体的值，
所以即便实际上没违反规则，它依然要牢骚不断。
这种警告偶尔也能帮程序员避免严重的失误。</p>
<p><code>static_cast</code>不对它转换的值进行检查；它相信程序员能运用得当。
可它也总有走眼的时候，所以如果吃不准，检查一下值。
应该尽可能避免显式类型转换
（通常也叫<em>强制类型转换（cast）</em>，用来提醒你它可能会把东西弄坏）。
尽量把不带检查的类型转换限制在系统底层。它们极易出错。</p>
<p>还有两种类型转换分别是：
<code>reinterpret_cast</code>，它简单地把对象按一连串字节对待；
<code>const_cast</code>用于“转掉<code>const</code>限制”。
对类型系统的审慎运用以及设计良好的库，都有助于在顶层软件中消除不带检查的类型转换。</p>
<p><a class="en-page-number" id="54"></a></p>
<h2 id="4.3">4.3 抽象类型 </h2>
<p><code>complex</code>和<code>Vector</code>这些被称为<em>实体类型</em>，因为表征数据是它们定义的一部分。
因此，它们与内置类型相仿。
相反，<em>抽象类型（abstract type）</em>是把用户和实现细节隔绝开的类型。
为此，要把接口和表征数据解耦，并且要摒弃纯局部变量。
既然对抽象类的表征数据（甚至其容量）一无所知，
就只能把它的对象分配在自由存储区(§4.2.2)，
并通过引用或指针访问它们(§1.7, §13.2.1)。</p>
<p>首先，我们定义<code>Container</code>类的接口，它将被设计成<code>Vector</code>更抽象的版本：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Container</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-keyword">virtual</span> <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span>) = <span class="hljs-number">0</span>;    <span class="hljs-comment">// 纯虚函数</span>
    <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>= <span class="hljs-number">0</span>;           <span class="hljs-comment">// const 成员函数 (§4.2.1)</span>
    <span class="hljs-keyword">virtual</span> ~<span class="hljs-built_in">Container</span>() {}                 <span class="hljs-comment">// 析构函数 (§4.2.2)</span>
};
</code></pre>
<p>该类是一个用于描述后续容器的纯接口。
<code>virtual</code>这个词的意思是“后续可能在从此类派生的类中被重新定义”。
用<code>virtual</code>声明的函数自然而然的被称为<em>虚函数（virtual function）</em>。
从<code>Container</code>派生的类要为<code>Container</code>接口提供实现。
古怪的<code>=0</code>语法意思是：此函数是<em>纯虚的（pure virtual）</em>；
就是说，某些继承自<code>Container</code>的类<em>必须</em>定义该函数。
因此，根本无法直接为<code>Container</code>类型定义对象。例如：</p>
<pre><code class="lang-cpp">Container c;                                <span class="hljs-comment">// 报错：抽象类没有自己的对象</span>
Container* p = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Vector_container</span>(<span class="hljs-number">10</span>);    <span class="hljs-comment">// OK：Container作为接口使用</span>
</code></pre>
<p><code>Container</code>只能用做作接口，
服务于那些给<code>operator[]()</code>和<code>size()</code>函数提供了实现的类。
带有虚函数的类被称为<em>抽象类（abstract class）</em>。</p>
<p><code>Container</code>可以这样用：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">use</span><span class="hljs-params">(Container&amp; c)</span>
</span>{
    <span class="hljs-type">const</span> <span class="hljs-type">int</span> sz = c.<span class="hljs-built_in">size</span>();

    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=sz; ++i)
        cout &lt;&lt; c[i] &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p>请注意<code>use()</code>在使用<code>Container</code>接口时对其实现细节一无所知。
它用到<code>size()</code>和<code>[ ]</code>，却完全不知道为它们提供实现的类型是什么。
为诸多其它类定义接口的类通常被称为<em>多态类型（polymorphic type）</em>。</p>
<p>正如常见的抽象类，<code>Container</code>也没有构造函数。毕竟它不需要初始化数据。
另一方面，<code>Container</code>有一个析构函数，并且还是<code>virtual</code>的，
以便让<code>Container</code>的派生类去实现它。
这对于抽象类也是常见的，因为它们往往通过引用或指针进行操作，
而借助指针销毁<code>Container</code>对象的人根本不了解具体用到了哪些资源。</p>
<p><a class="en-page-number" id="55"></a></p>
<p>抽象类<code>Container</code>仅仅定义接口，没有实现。
想让它发挥作用，就需要弄一个容器去实现它接口规定的那些函数。
为此，可以使用一个实体类<code>Vector</code>:</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector_container</span> : <span class="hljs-keyword">public</span> Container { <span class="hljs-comment">// Vector_container 实现了 Container</span>
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector_container</span>(<span class="hljs-type">int</span> s) : <span class="hljs-built_in">v</span>(s) { }      <span class="hljs-comment">// s个元素的Vector</span>
    ~<span class="hljs-built_in">Vector_container</span>() {}

    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) <span class="hljs-keyword">override</span> { <span class="hljs-keyword">return</span> v[i]; }
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-keyword">return</span> v.<span class="hljs-built_in">size</span>(); }
<span class="hljs-keyword">private</span>:
    Vector v;
};
</code></pre>
<p><code>:public</code>可以读作“派生自”或者“是……的子类型”。
我们说<code>Vetor_container</code><em>派生（derived）</em>自<code>Container</code>，
并且<code>Container</code>是<code>Vetor_container</code>的<em>基类（base）</em>。
还有术语把<code>Vetor_container</code>和<code>Container</code>分别称为
<em>子类（subclass）</em>和<em>亲类（superclass）</em>。
我们说派生类继承了其基类的成员，
所以这种基类和派生类的关系通常被称为<em>继承（inheritance）</em>。</p>
<p>我们这里的<code>operator[]()</code>和<code>size()</code><em>覆盖（override）
</em>了基类<code>Container</code>中对应的成员。
我明确使用<code>override</code>表达了这个意向。
这里的<code>override</code>可以省略，但是明确使用它，可以让编译器查错，
比如函数名拼写错误，或者<code>virtual</code>函数和被其覆盖的函数之间的细微类型差异等等。
在较大的类体系中明确使用<code>override</code>格外有用，否则就难以搞清楚覆盖关系。</p>
<p>这里的析构函数（<code>~Vector_container()</code>）
覆盖了基类的析构函数（<code>~Container()</code>）。
请注意，其成员的析构函数（<code>~Vector</code>）
被该类的析构函数（<code>~Vector_container()</code>）隐式调用了。</p>
<p>对于<code>use(Container&amp;)</code>这类函数，使用<code>Container</code>时不必了解其实现细节，
其它函数要创建具体对象供它操作的。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">g</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-function">Vector_container <span class="hljs-title">vc</span><span class="hljs-params">(<span class="hljs-number">10</span>)</span></span>; <span class="hljs-comment">// 十个元素的Vector</span>
    <span class="hljs-comment">// ... 填充 vc ...</span>
    <span class="hljs-built_in">use</span>(vc);
}
</code></pre>
<p>由于<code>use()</code>只了解<code>Container</code>接口而非<code>Vector_container</code>，
它就可以对<code>Container</code>的其它实现同样有效。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">List_container</span> : <span class="hljs-keyword">public</span> Container { <span class="hljs-comment">// List_container implements Container</span>
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">List_container</span>() { }    <span class="hljs-comment">// empty List</span>
    <span class="hljs-built_in">List_container</span>(initializer_list&lt;<span class="hljs-type">double</span>&gt; il) : ld{il} { }
    ~<span class="hljs-built_in">List_container</span>() {}
</code></pre>
<p><a class="en-page-number" id="56"></a></p>
<pre><code class="lang-cpp">    <span class="hljs-type">double</span>&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) <span class="hljs-keyword">override</span>;
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">override</span> </span>{ <span class="hljs-keyword">return</span> ld.<span class="hljs-built_in">size</span>(); }
<span class="hljs-keyword">private</span>:
    std::list&lt;<span class="hljs-type">double</span>&gt; ld;   <span class="hljs-comment">// double类型的（标准库）列表 (§11.3)</span>
};

<span class="hljs-type">double</span>&amp; List_container::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i)
{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>&amp; x : ld) {
        <span class="hljs-keyword">if</span> (i==<span class="hljs-number">0</span>)
            <span class="hljs-keyword">return</span> x;
        --i;
    }
    <span class="hljs-keyword">throw</span> out_of_range{<span class="hljs-string">&quot;List container&quot;</span>};
}
</code></pre>
<p>此处的表征数据是标准库的<code>list&lt;double&gt;</code>。
一般来说，我不会给<code>list</code>容器实现取下标操作，
因为<code>list</code>取下标操作的性能比<code>vector</code>差得一塌糊涂。
但我在这里只是展示一个略有点偏激的实现。</p>
<p>某函数可以创建一个<code>List_container</code>，然后让<code>use()</code>去使用它：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">h</span><span class="hljs-params">()</span>
</span>{
    List_container lc = { <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>, <span class="hljs-number">8</span>, <span class="hljs-number">9</span> };
    <span class="hljs-built_in">use</span>(lc);
}
</code></pre>
<p>这里的重点是，<code>use(Container&amp;)</code>不知道它的参数是<code>Vector_container</code>、
<code>List_container</code>，还是什么其它容器类型；它也没必要知道。
它可以使用任意类型的<code>Container</code>。它知道的仅仅是<code>Container</code>定义的接口。
因此，就算是<code>List_container</code>的实现发生改变，
甚至是使用一个派生自<code>Container</code>的全新的类，
<code>use(Container&amp;)</code>都不需要重新编译。</p>
<p>与这种灵活性密不可分的是：必须通过指针或引用去操作对象(§5.2, §13.2.1)。</p>
<h2 id="4.4">4.4 虚函数 </h2>
<p>复习一下<code>Container</code>的用法：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">use</span><span class="hljs-params">(Container&amp; c)</span>
</span>{
    <span class="hljs-type">const</span> <span class="hljs-type">int</span> sz = c.<span class="hljs-built_in">size</span>();

    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i!=sz; ++i)
        cout &lt;&lt; c[i] &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p><code>use()</code>中的<code>c[i]</code>调用是怎么解析到对应的<code>operator[]()</code>呢？
当<code>h()</code>调用<code>use()</code>时，<code>List_container</code>的<code>operator[]()</code>必须被调用。
<code>g()</code>调用<code>use()</code>时，<code>Vector_container</code>的<code>operator[]()</code>必须被调用。</p>
<p><a class="en-page-number" id="57"></a></p>
<p>想要实现这种解析，<code>Container</code>对象必须包含某种信息，
以便在运行时找到正确的待调用函数。
常见的实现技术是：编译器把虚函数的名称转换成一个指向函数指针表的索引。
这个表格通常被称为<em>虚函数表（virtual function table）</em>，或者简称<code>vtbl</code>。
每个带有虚函数的类都有自己的<code>vtbl</code>以确认其虚函数。这可以图示如下：</p>
<p><img src="img/ch04_02.png" alt="vtbl"></p>
<p><code>vtbl</code>中的函数能够正确地使用其对象，
即便调用者对该对象的容量以及数据布局全都一无所知。
调用者的实现仅需要知道某个<code>Container</code>中指向
<code>vtbl</code>指针的位置以及每个待用虚函数的索引。
虚函数调用机制几乎能做到与“常规函数调用”机制同样高效（性能差别不到25%）。
其空间消耗是带有虚函数的类的每个对象一个指针，再加上每个类一个<code>vtbl</code>。</p>
<h2 id="4.5">4.5 类层次 </h2>
<p><code>Container</code>是个很简略的类层次示例。
<em>类层次（class hierarchy）</em>是一组类，
通过派生（比如 <code>: public</code>）创建，按棱形格次序排列。
类层次用来表示具有层次关系的概念。
比如“消防车是一种卡车，卡车是一种车”，以及“笑脸图是一种圆圈，圆圈是一种图形”。
巨大的，具有成百上千个类，还即深又宽的类体系也属平常。
作为一个半真实的经典案例，考虑一下屏幕上的形状：</p>
<p><img src="img/ch04_03.png" alt="shapes"></p>
<p>箭头代表继承关系。例如：<code>Circle</code>类派生自<code>Shape</code>类。</p>
<p><a class="en-page-number" id="58"></a></p>
<p>习惯上，类层次把最基础的类作为根，自上而下朝向派生（定义更晚）类生长。
为了用代码表示这个简单的图示，就必须先声明一个类，以定义所有类型的通用属性：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Shape</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">virtual</span> Point <span class="hljs-title">center</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>=<span class="hljs-number">0</span>;    <span class="hljs-comment">// 纯虚函数</span>
    <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">move</span><span class="hljs-params">(Point to)</span> </span>=<span class="hljs-number">0</span>;

    <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>= <span class="hljs-number">0</span>;      <span class="hljs-comment">// 在“画布”上绘制</span>
    <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">rotate</span><span class="hljs-params">(<span class="hljs-type">int</span> angle)</span> </span>= <span class="hljs-number">0</span>;

    <span class="hljs-keyword">virtual</span> ～<span class="hljs-built_in">Shape</span>() {}                <span class="hljs-comment">// 析构函数</span>
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>自然而然的，此接口是个抽象类：就表征数据而言，各种<code>Shape</code>之间
（除指向<code>vtbl</code>位置的指针之外）<em>毫无（nothing）</em>共通之处。
根据这个定义，可以写一个通用的函数操纵一个vector，其中的元素是指向图形的指针：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">rotate_all</span><span class="hljs-params">(vector&lt;Shape*&gt;&amp; v, <span class="hljs-type">int</span> angle)</span>   <span class="hljs-comment">// 把v的元素旋转给定角度</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p : v)
        p-&gt;<span class="hljs-built_in">rotate</span>(angle);
}
</code></pre>
<p>要定义特定的图形，必须指明它是个<code>Shape</code>，定义它特有的属性（包括其虚函数）：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Circle</span> : <span class="hljs-keyword">public</span> Shape {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Circle</span>(Point p, <span class="hljs-type">int</span> rad);       <span class="hljs-comment">// 构造函数</span>

    <span class="hljs-function">Point <span class="hljs-title">center</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">override</span>
    </span>{
        <span class="hljs-keyword">return</span> x;
    }
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">move</span><span class="hljs-params">(Point to)</span> <span class="hljs-keyword">override</span>
    </span>{
        x = to;
    }

    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">rotate</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span> <span class="hljs-keyword">override</span> </span>{}    <span class="hljs-comment">// 优美且简洁的算法</span>
<span class="hljs-keyword">private</span>:
    Point x;    <span class="hljs-comment">// 圆心</span>
    <span class="hljs-type">int</span> r;      <span class="hljs-comment">// 半径</span>
};
</code></pre>
<p>截至目前，<code>Shape</code>和<code>Circle</code>的例子跟<code>Container</code>相比还没有什么亮点，
请接着往下看：</p>
<p><a class="en-page-number" id="59"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Smiley</span> : <span class="hljs-keyword">public</span> Circle {  <span class="hljs-comment">// 用圆圈作为笑脸的基类</span>
    <span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Smiley</span>(Point p, <span class="hljs-type">int</span> rad) : Circle{p,rad}, mouth{<span class="hljs-literal">nullptr</span>} { }
    ~<span class="hljs-built_in">Smiley</span>() {
        <span class="hljs-keyword">delete</span> mouth;
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p : eyes)
            <span class="hljs-keyword">delete</span> p;
    }

    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">move</span><span class="hljs-params">(Point to)</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">draw</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> <span class="hljs-keyword">override</span></span>;
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">rotate</span><span class="hljs-params">(<span class="hljs-type">int</span>)</span> <span class="hljs-keyword">override</span></span>;

    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">add_eye</span><span class="hljs-params">(Shape* s)</span>
    </span>{
        eyes.<span class="hljs-built_in">push_back</span>(s);
    }
    <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">set_mouth</span><span class="hljs-params">(Shape* s)</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">virtual</span> <span class="hljs-type">void</span> <span class="hljs-title">wink</span><span class="hljs-params">(<span class="hljs-type">int</span> i)</span></span>;   <span class="hljs-comment">// 让第i只眼做“飞眼”</span>
    <span class="hljs-comment">// ...</span>
<span class="hljs-keyword">private</span>:
    vector&lt;Shape*&gt; eyes;        <span class="hljs-comment">// 一般是两只眼睛</span>
    Shape* mouth;
};
</code></pre>
<p><code>vector</code>的成员函数<code>push_bach()</code>把参数复制进<code>vector</code>（这里是<code>eyes</code>），
让它成为末尾的元素，并且把 vector 的容量增一。</p>
<p>现在，可以利用<code>Smiley</code>的基类和成员函数<code>draw()</code>
的调用来定义<code>Smiley::draw()</code>了：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">Smiley::draw</span><span class="hljs-params">()</span> <span class="hljs-type">const</span>
</span>{
    Circle::<span class="hljs-built_in">draw</span>();
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p : eyes)
        p-&gt;<span class="hljs-built_in">draw</span>();
    mouth-&gt;<span class="hljs-built_in">draw</span>();
}
</code></pre>
<p>请注意，<code>Smiley</code>把它的眼睛保存在一个标准库的<code>vector</code>里，
并且会在析构函数中把它们销毁。
<code>Shape</code>的析构函数是<code>virtual</code>的，而<code>Smiley</code>又覆盖了它。
虚析构函数对于抽象类来说是必须的，
因为操控派生类的对象通常是借助抽象基类提供的接口进行的。
具体地说，它可能是通过其基类的指针被销毁的。
然后，虚函数调用机制确保正确析构函数被调用。
该析构函数则会隐式调用其基类和成员变量的析构函数。</p>
<p><a class="en-page-number" id="60"></a></p>
<p>在这个简化过的例子中，把眼睛和嘴巴准确放置到代表脸的圆圈中，是程序员的的任务。</p>
<p>在以派生方式定义一个新类时，我们可以添加新的 成员变量 或/和 运算。
这带来了极佳的灵活性，同时又给逻辑混乱和不良设计提供了温床。</p>
<h3 id="4.5.1">4.5.1 层次的益处 </h3>
<p>类的层次结构有两个益处：</p>
<ul>
<li><p><em>接口继承（interface inheritance）</em>：
  派生类对象可以用在任何基类对象胜任的位置。
  就是说，基类充当了派生类的接口。
  <code>Container</code>和<code>Shape</code>这两个类就是例子。
  这种类通常是抽象类。</p>
</li>
<li><p><em>实现继承（implementation inheritance）</em>：
  基类的函数和数据直接就是派生类实现的一部分。
  <code>Smiley</code>对<code>Circle</code>的构造函数、<code>Circle::draw()</code>的调用就是这方面的例子。
  这种基类通常具有成员变量和构造函数。</p>
</li>
</ul>
<p>实体类——尤其是带有少量表征数据那些——跟内置类型非常类似：
通常作为局部变量定义，通过名称进行访问，复制来复制去的，凡此种种。
位于类层次结构中那些就不一样了：
它们通常用<code>new</code>分配在自由存储区中，通过指针或引用访问它们。
举个例子，有这么个函数，它从输入流读取描述图形的数据，
然后构造对应的<code>Shape</code>对象：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">enum class</span> <span class="hljs-title class_">Kind</span> { circle, triangle, smiley };

<span class="hljs-function">Shape* <span class="hljs-title">read_shape</span><span class="hljs-params">(istream&amp; is)</span>  <span class="hljs-comment">// 从输入流is读取图形描述</span>
</span>{
    <span class="hljs-comment">// ... 从 is 读取图形概要信息，找到其类型（Kind） k ...</span>

    <span class="hljs-keyword">switch</span> (k) {
    <span class="hljs-keyword">case</span> Kind::circle:
        <span class="hljs-comment">// 把圆圈的数据 {Point,int} 读取到p和r</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Circle{p,r};
    <span class="hljs-keyword">case</span> Kind::triangle:
        <span class="hljs-comment">// 把三角形的数据 {Point,Point,Point} 读取到p1、p2、和p3</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Triangle{p1,p2,p3};
    <span class="hljs-keyword">case</span> Kind::smiley:
        <span class="hljs-comment">// 把笑脸的数据 {Point,int,Shape,Shape,Shape} 读取到p、r、e1、e2、和m</span>
        Smiley* ps = <span class="hljs-keyword">new</span> Smiley{p,r};
        ps-&gt;<span class="hljs-built_in">add_eye</span>(e1);
        ps-&gt;<span class="hljs-built_in">add_eye</span>(e2);
        ps-&gt;<span class="hljs-built_in">set_mouth</span>(m);
        <span class="hljs-keyword">return</span> ps;
    }
}
</code></pre>
<p>某个程序可以这样使用此图形读取器：</p>
<p><a class="en-page-number" id="61"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">()</span>
</span>{
    std::vector&lt;Shape*&gt; v;
    <span class="hljs-keyword">while</span> (cin)
        v.<span class="hljs-built_in">push_back</span>(<span class="hljs-built_in">read_shape</span>(cin));
    <span class="hljs-built_in">draw_all</span>(v);        <span class="hljs-comment">// 为每个元素调用 draw()</span>
    <span class="hljs-built_in">rotate_all</span>(v,<span class="hljs-number">45</span>);   <span class="hljs-comment">// 为每个元素调用 rotate(45)</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> p : v)    <span class="hljs-comment">// 别忘了销毁元素（指向的对象）</span>
        <span class="hljs-keyword">delete</span> p;
}
</code></pre>
<p>显而易见，这个例子被简化过了——尤其是错误处理相关的内容——
但它清晰地表明了，<code>user()</code>函数对其所操纵图形的类型一无所知。
<code>user()</code>的代码仅需要编译一次，在程序加入新的<code>Shape</code>之后可以继续使用。
请留意，没有任何图形的指针流向了<code>user()</code>之外，因此<code>user()</code>就要负责回收它们。
这实用运算符<code>delete</code>完成，且严重依赖<code>Shape</code>的虚析构函数。
因为这个析构函数是虚的，<code>delete</code>调用的是距基类最远的派生类里的那个。
这至关重要，因为可能获取了各式各样有待释放的资源（比如<span title="一般翻译为“文件句柄”，我很讨厌“句柄”这词，它让我在初学Windows编程时困惑了很久，我在实践了一段时间之后，慢慢理解到，这个英文 “handle” 是很直白的，意思就是“把手”，我觉得翻译成“把手”要比“句柄”好得多。
参阅了[为什么handle会被翻译成句柄？ - 薛非的回答 - 知乎](https://www.zhihu.com/question/22950899/answer/25197413)后，我决定改一个基本能望文生义的两字词汇去替代“句柄”，“handle” 本义的“柄”作为名词词尾保留，同时不使用常见单词，因为会导致“难以作为关键词搜索”的问题。首字存在多个备选：

“操”，取自“操作”一词，感觉最适宜，但因为有人把“操”污名化出“肏”的意义，为免给用户带来“解释吧，没必要；不解释吧，又让文盲耻笑”的困境，不得不忍痛放弃了；
“持”，初望难免有“静止不动”的意味（例如“僵持”），不合适；
“握”，太直白，容易跟常规词汇冲突。

最后考虑了“执柄”，“执”乍看就是拿着，后续可以动也可以不动，有成词但不常见，故选它，希望初次接触这个概念的人不至于太过困惑。">文件执柄</span><sup><a href="#fn_2" id="reffn_2">2</a></sup>、锁及输出流）。
在本例中，<code>Smiley</code>要删除其<code>eyes</code>和<code>mouth</code>的对象。
删完这些之后，它又去调用<code>Circle</code>的析构函数。
对象的构建通过构造函数“自下而上”（从基类开始），
而销毁通过虚构函数“从顶到底”（从派生类开始）。</p>
<h3 id="4.5.2">4.5.2 层次辨向 </h3>
<p><code>read_shape()</code>函数返回<code>Shape*</code>，以便我们对所有<code>Shape</code>一视同仁。
但是，如果我们想调用某个派生类特有的函数，
比方说<code>Smiley</code>里的<code>wink()</code>，该怎么办呢？
我们可以用<code>dynamic_cast</code>运算符问这个问题
“这个<code>Shape</code>对象是<code>Smiley</code>类型的吗？”：</p>
<pre><code class="lang-cpp">Shape* ps {<span class="hljs-built_in">read_shape</span>(cin)};

<span class="hljs-keyword">if</span> (Smiley* p = <span class="hljs-built_in">dynamic_cast</span>&lt;Smiley*&gt;(ps)) {    <span class="hljs-comment">// ... ps指向一个 Smiley 吗？ ...</span>
    <span class="hljs-comment">// ... 是 Smiley；用它</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// ... 不是 Smiley，其它处理 ...</span>
}
</code></pre>
<p>在运行时，如果<code>dynamic_cast</code>的参数（此处是<code>ps</code>）指向的对象不是期望的类型
（此处是<code>Smiley</code>）或其派生类，<code>dynamic_cast</code>就返回<code>nullptr</code>。</p>
<p>当一个指向其它派生类对象的指针是有效参数时，
我们把<code>dynamic_cast</code>用于指针类型。
然后测试结果是否为<code>nullptr</code>。
这种测试一般放在条件表达式的初始化参数位置，很便利。</p>
<p>如果其它类型不可接受，我们就直接把<code>dynamic_cast</code>用于引用类型。
如果该对象不是期望的类型，<code>dynamic_cast</code>抛出一个<code>bad_cast</code>异常：</p>
<pre><code class="lang-cpp">Shape* ps {<span class="hljs-built_in">read_shape</span>(cin)};
Smiley&amp; r {<span class="hljs-built_in">dynamic_cast</span>&lt;Smiley&amp;&gt;(*ps)}; <span class="hljs-comment">// 某处可以捕捉到 std::bad_cast</span>
</code></pre>
<p>有节制地使用<code>dynamic_cast</code>可以让代码整洁。</p>
<p><a class="en-page-number" id="62"></a></p>
<p>如果能避免用到类型信息，就可以写出简洁且高效的代码，
但类型信息系偶尔会被丢掉而且必须找回来。
发生这种情况，通常是我们把对象传给了一个系统，
该系统以某个特定基类指定的接口接收了这个对象。
当该系统后续把这个对象传回来时，我们可能要找回初始的类型。
类似于<code>dynamic_cast</code>的运算被称为“属于……类别”或者“是……实例”运算。</p>
<h3 id="4.5.3">4.5.3 避免资源泄漏 </h3>
<p>经验丰富的程序员可能注意到了我有三个纰漏：</p>
<ul>
<li>写<code>Smiley</code>的程序员可能忘记<code>delete</code>指向<code>mouth</code>的指针</li>
<li><code>read_shape()</code>的用户可能忘记<code>delete</code>返回的指针</li>
<li><code>Shape</code>指针容器的所有者可能忘记<code>delete</code>它们指向的对象</li>
</ul>
<p>从这个意义上讲，指向自由存储区中对象的指针是危险的：
“直白老旧的指针（plain old pointer）”不该用于表示所有权。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span>
</span>{
    Shape* p = <span class="hljs-keyword">new</span> Circle{Point{<span class="hljs-number">0</span>,<span class="hljs-number">0</span>},<span class="hljs-number">10</span>};
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">if</span> (x&lt;<span class="hljs-number">0</span>) <span class="hljs-keyword">throw</span> Bad_x{}; <span class="hljs-comment">// 资源泄漏潜在危险</span>
    <span class="hljs-keyword">if</span> (x==<span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;       <span class="hljs-comment">// 资源泄漏潜在危险</span>
    <span class="hljs-comment">// ...</span>
    <span class="hljs-keyword">delete</span> p;
}
</code></pre>
<p>除非<code>x</code>为正数，否则就会导致资源泄漏。
把<code>new</code>的结果赋值给“裸指针”就是自找麻烦。</p>
<p>这类问题有一个简单的解决方案：在需要释放操作时，
使用标准库的<code>unique_ptr</code>(§13.2.1) 而非“裸指针”：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">Smiley</span> : <span class="hljs-keyword">public</span> Circle {
    <span class="hljs-comment">// ...</span>
<span class="hljs-keyword">private</span>:
    vector&lt;unique_ptr&lt;Shape&gt;&gt; eyes; <span class="hljs-comment">// 一般是两只眼睛</span>
    unique_ptr&lt;Shape&gt; mouth;
};
</code></pre>
<p>这是一个示例，展示简洁、通用、高效的资源管理(§5.3)技术。</p>
<p>这个修改有个良性的副作用：我们不需要再为<code>Smiley</code>定义析构函数了。
编译器会隐式生成一个，以便将<code>vector</code>中的<code>unique_ptr</code>(§5.3)销毁。
使用<code>unique_ptr</code>的代码跟用裸指针的代码在效率方面完全一致。</p>
<p>重新审视<code>read_shape()</code>的使用：</p>
<pre><code class="lang-cpp"><span class="hljs-function">unique_ptr&lt;Shape&gt; <span class="hljs-title">read_shape</span><span class="hljs-params">(istream&amp; is)</span> <span class="hljs-comment">// 从输入流is读取图形描述</span>
</span>{
    <span class="hljs-comment">// ... 从 is 读取图形概要信息，找到其类型（Kind） k ...</span>
</code></pre>
<p><a class="en-page-number" id="63"></a></p>
<pre><code class="lang-cpp">
    <span class="hljs-keyword">switch</span> (k) {
    <span class="hljs-keyword">case</span> Kind::circle:
        <span class="hljs-comment">// 把圆圈的数据 {Point,int} 读取到p和r</span>
        <span class="hljs-keyword">return</span> unique_ptr&lt;Shape&gt;{<span class="hljs-keyword">new</span> Circle{p,r}};
    <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">()</span>
</span>{
    vector&lt;unique_ptr&lt;Shape&gt;&gt; v;
    <span class="hljs-keyword">while</span> (cin)
        v.<span class="hljs-built_in">push_back</span>(<span class="hljs-built_in">read_shape</span>(cin));
    <span class="hljs-built_in">draw_all</span>(v);        <span class="hljs-comment">// 为每个元素调用 draw()</span>
    <span class="hljs-built_in">rotate_all</span>(v,<span class="hljs-number">45</span>);   <span class="hljs-comment">// 为每个元素调用 rotate(45)</span>
} <span class="hljs-comment">// 所有 Shape 都隐式销毁了</span>
</code></pre>
<p>现在每个对象都被一个<code>unique_ptr</code>持有，当不再需要这个<code>unique_ptr</code>，
也就是它离开作用域的时候，就会销毁持有的对象。</p>
<p>想让<code>unique_ptr</code>版本的<code>user()</code>正常运作，
就需要能够接受<code>vector&lt;unique_ptr&lt;Shape&gt;&gt;</code>版本的
<code>draw_all()</code>和<code>rotate_all()</code>。
写很多这种<code>_all()</code>函数很烦冗，所以 §6.3.2 会介绍一个替代方案。</p>
<h2 id="4.6">4.6 忠告 </h2>
<ul>
<li>[1] 用代码直接表达意图；§4.1; [CG: P.1]。</li>
<li>[2] 实体类型是最简单的类。情况许可的时候，
  请优先用实体类，而非更复杂的类或者普通的数据结构 §4.2; [CG: C.10]。</li>
<li>[3] 用实体类去表示简单的概念；§4.2。</li>
<li>[4] 对于性能要求严苛的组件，优先用实体类，而不是选择类层次；§4.2。</li>
<li>[5] 定义构造函数去处理对象的初始化；§4.2.1, §5.1.1; [CG: C.40] [CG: C.41]。</li>
<li>[6] 只在一个函数需要直接访问类的表征数据时，把它定义为成员函数；§4.2.1; [CG: C.4]。</li>
<li>[7] 自定义运算符的主要用途应该是模拟传统运算；§4.2.1; [CG: C.160]。</li>
<li>[8] 为对称运算符使用非成员函数；§4.2.1; [CG: C.161]。</li>
<li>[9] 把不修改对象状态的成员函数定义为<code>const</code>；§4.2.1。</li>
<li>[10] 如果构造函数申请了资源，这个类就需要虚构函数去释放这个资源；§4.2.2; [CG: C.20]。</li>
<li>[11] 避免使用“裸的”<code>new</code>和<code>delete</code>操作；§4.2.2; [CG: R.11]。</li>
<li>[12] 利用资源操控器和 RAII 去管理资源；§4.2.2; [CG: R.1]。</li>
<li>[13] 如果类是容器，请给它定义一个初始化列表构造函数；§4.2.3; [CG: C.103]。</li>
<li>[14] 需要接口和实现完全分离的时候，请用抽象类作为接口；§4.3; [CG: C.122]。</li>
<li>[15] 请通过指针和引用访问多态对象；§4.3。</li>
<li>[16] 抽象类通常不需要构造函数；§4.3; [CG: C.126]。</li>
<li>[17] 对于与生俱来就具有层次结构的概念，请使用类层次结构表示它们；§4.5。</li>
</ul>
<p><a class="en-page-number" id="64"></a></p>
<ul>
<li>[18] 带有虚函数的类，应该定义虚析构函数；§4.5; [CG: C.127]。</li>
<li>[19] 在较大的类层次中，显式用<code>override</code>进行覆盖；§4.5.1; [CG: C.128]。</li>
<li>[20] 设计类层次的时候，要分清实现继承和接口继承；§4.5.1; [CG: C.129]。</li>
<li>[21] 在不可避免要在类层次中进行辨别的时候，使用<code>dynamic_cast</code>；§4.5.2;[CG:C.146]。</li>
<li>[22] 当“转换目标不属于所需的类”需要报错时，就把<code>dynamic_cast</code>用于引用类型；
  §4.5.2; [CG: C.147]。</li>
<li>[23] 如果“转换目标不属于所需的类”可接受，就把<code>dynamic_cast</code>用于指针类型；
  §4.5.2; [CG: C.148]。</li>
<li>[24] 对于通过<code>new</code>创建的对象，用<code>unique_ptr</code>和<code>shared_ptr</code>避免忘记<code>delete</code>；§4.5.3; [CG: C.149]。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 请原谅我硬搞出来一个词，因为它在本章出现了太多次。其实按我的理解，更合理的说法是“用到的所有数据”，本质上是在说“数据”，可是仅译成“数据”容易跟“data”混淆；而“representation”在这是名词“表示”的意思，说成“用到的所有数据的表示”又太拗口。如果改成“数据表示”，容易被误会成<strong>主谓短语</strong>，用在句子的语境中，容易引起误解；改成“表示数据”又容易被误会成<strong>动宾短语</strong>，引发类似的误解。所以我把“表示”改成了“表征”，这样无论理解成“名词+名词”还是“形容词+名词”的<strong>偏正短语</strong>，意思都是“什么什么样的数据”，同时还能跟单纯的“数据（data）”区分开来。—— 译者注<a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>
<blockquote id="fn_2">
<sup>2</sup>. 一般翻译为“文件句柄”，我很讨厌“句柄”这词，它让我在初Windows编程时困惑了很久，我在实践了一段时间之后，慢慢理解到，这个英文<strong>handle</strong>是很直白的，意思就是“把手”，我觉得翻译成“把手”要比“句柄”好得多。参阅了<a href="https://www.zhihu.com/question/22950899/answer/25197413" target="_blank">[为什么handle会被翻译成句柄？ - 薛非的回答 - 知乎]</a>后，我决定改一个基本能望文生义的两字词汇去替代“句柄”，<strong>handle</strong>本义的“柄”作为名词词尾保留，同时不使用常见单词，因为会导致“难以作为关键词搜索”的问题。首字存在多个备选：1，“操”，取自“操作”一词，感觉最适宜，但因为有人把“操”污名化出“肏”的意义，为免给用户带来“解释吧，没必要；不解释吧，又让文盲耻笑”的困境，不得不忍痛放弃了；2，“持”，初望难免有“静止不动”的意味（例如“僵持”），不合适；3，“握”，太直白，容易跟常规词汇冲突。最后考虑了“执柄”，“执”乍看就是拿着，后续可以动也可以不动，有成词但不常见，故选它，希望初次接触这个概念的人不至于太过困惑。—— 译者注<a href="#reffn_2" title="Jump back to footnote [2] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch03.html" class="navigation navigation-prev " aria-label="Previous page: 3 模块化">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch05.html" class="navigation navigation-next " aria-label="Next page: 5 基本操作">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":4,"title":"4 类","level":"1.6","depth":1,"next":{"title":"5 基本操作","level":"1.7","depth":1,"path":"ch05.md","ref":"ch05.md","articles":[]},"previous":{"title":"3 模块化","level":"1.5","depth":1,"path":"ch03.md","ref":"ch03.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch04.md","mtime":"2023-02-12T09:23:08.805Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

